#include "android.h"
#include "config.h"
#include "hashmap.h"
#include <malloc.h>
#include <netinet/in.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <unistd.h>

static int listenfd = -1;
static int epfd = -1;
static struct sockaddr_in servaddr;
static const int fd_size = 10;
#define EPOLL_EVENT_SIZE 20
static struct epoll_event events[EPOLL_EVENT_SIZE];

static void *handle_recv(void *arg);
static void add_event(int fd, int state);
static void delete_event(int fd);
// static void modify_event(int fd, int state);

hashmap_t *dev_connfd_map;
hashmap_t *connfd_dev_map;
hashmap_t *connfd_task_map;
hashmap_t *connfd_taskmutex_map;
hashmap_t *connfd_mutex_map;

void android_init() {
  config_t *conf = readconf();
  listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

  if (listenfd == -1) {
    perror("android listen");
    exit(-1);
  }

  memset(&servaddr, 0, sizeof(struct sockaddr_in));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(conf->android_port);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  int ret;

  ret =
      bind(listenfd, (struct sockaddr *)&servaddr, sizeof(struct sockaddr_in));

  if (ret == -1) {
    perror("android bind");
    close(listenfd);
    exit(-1);
  }

  ret = listen(listenfd, conf->android_maxclientnum);

  if (ret == -1) {
    perror("android listen");
    close(listenfd);
    exit(-1);
  }

  dev_connfd_map = create_hashmap(NULL, NULL);
  connfd_dev_map = create_hashmap(NULL, NULL);

  connfd_task_map = create_hashmap(NULL, NULL);
  connfd_taskmutex_map = create_hashmap(NULL, NULL);
  connfd_mutex_map = create_hashmap(NULL, NULL);

  epfd = epoll_create(fd_size);
  add_event(listenfd, EPOLLIN);
}

void android_startup() {
  while (true) {
    int ret = epoll_wait(epfd, events, EPOLL_EVENT_SIZE, -1);

    for (int i = 0; i < ret; ++i) {
      if (events[i].data.fd == listenfd) {
        // accept
        AndroidConn conn;

        conn.connfd =
            accept(listenfd, (struct sockaddr *)&conn.caddr, &conn.addrlen);

        if (conn.connfd <= 0) {
          fprintf(stderr, "[%s, %d] %s", __FILE__, __LINE__,
                  "accept android clietn failed\n");
          continue;
        }

        add_event(conn.connfd, EPOLLIN);
      } else {
        int connfd = events[i].data.fd;

        if (events[i].events & EPOLLIN) {
          // has something to receive
          pthread_t tid;
          pthread_create(&tid, NULL, handle_recv, (void *)(long)connfd);
        }
      }
    }
  }
}

void *handle_recv(void *arg) {
  int connfd = (int)(long)arg;
  char buf[16];

  recv(connfd, buf, sizeof(buf), 0);

  char *resp;

  if (buf[0] == MsgStatus.ANDROID_ONLINE) {
    int total_size;

    AndroidOnlineMsg *online_msg = (AndroidOnlineMsg *)buf;

    connfd_dev_map->put(connfd_dev_map, (void *)(long)connfd,
                        (void *)(long)online_msg->dev_id);
    dev_connfd_map->put(connfd_dev_map, (void *)(long)online_msg->dev_id,
                        (void *)(long)connfd);

    pthread_mutex_t *mutex = malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init(mutex, NULL);
    connfd_mutex_map->put(connfd_mutex_map, (void *)(long)connfd, mutex);

    pthread_mutex_t *mutex2 = malloc(sizeof(pthread_mutex_t));
    pthread_mutex_init(mutex2, NULL);
    connfd_taskmutex_map->put(connfd_taskmutex_map, (void *)(long)connfd,
                              mutex2);

    resp = mkresp2android(Resp2AndroidType.ONLINE, 0, NULL, &total_size);
    send_android_msg(connfd, resp, total_size);
    free(resp);
  } else if (buf[0] == MsgStatus.ANDROID_HEARTBEAT) {
    // do nothing
  } else if (buf[0] == MsgStatus.RESP_FROM_ANDROID) {
    if (buf[1] == AndroidRespType.READY_RECV) {
      musictask_t *task =
          connfd_task_map->get(connfd_task_map, (void *)(long)connfd);
      musicvec_t *vecptr = &task->musicvec;

      config_t *conf = readconf();
      char *file_path = conf->music_file_path;
      int path_len = strlen(file_path);

      if (file_path[path_len - 1] != '/') {
        file_path[path_len++] = '/';
        file_path[path_len] = '\0';
      }

      char filename[127] = {0};

      pthread_mutex_t *mutex_ptr =
          connfd_mutex_map->get(connfd_mutex_map, (void *)(long)connfd);
      pthread_mutex_lock(mutex_ptr);

      for (int i = 0; i < vecptr->len; ++i) {
        strncpy(filename, conf->music_file_path, sizeof(filename));
        strncat(filename, file_path, path_len);
        FILE *fp = fopen(file_path, "r");

        if (!fp) {
          delete_conn(connfd);
        }

        send_file(connfd, fp);
        fclose(fp);
      }

      pthread_mutex_unlock(mutex_ptr);

      // free task mutex
      pthread_mutex_t *task_mutex =
          connfd_taskmutex_map->get(connfd_taskmutex_map, (void *)(long)connfd);
      pthread_mutex_unlock(task_mutex);

    } else if (buf[1] == AndroidRespType.DEV_ERR) {
      delete_conn(connfd);
    }
  }

  return NULL;
}

void add_event(int fd, int state) {
  struct epoll_event ev;
  ev.data.fd = fd;
  ev.events = state;
  epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);
}

void delete_event(int fd) { epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); }

/*void modify_event(int fd, int state)
{
    struct epoll_event ev;

    ev.data.fd = fd;
    ev.events = state;
    epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev);
}*/

char *mkresp2android(uint8_t resp_type, uint8_t file_num,
                     const uint8_t file_sizes[], int *psize) {
  char *ret = NULL;
  uint8_t status = MsgStatus.RESP_2_ANDROID;

  if (resp_type == Resp2AndroidType.ONLINE) {
    ret = calloc(3, sizeof(char));
    ret[0] = status;
    ret[1] = resp_type;
    ret[2] = ret[1];
    *psize = 3;
  } else if (resp_type == Resp2AndroidType.TRANSFER) {
    int n = 3 + file_num;

    ret = calloc(n + 1, sizeof(char));
    ret[0] = status;
    ret[1] = resp_type;
    ret[2] = file_num;

    int i = 2;

    while (++i < n) {
      ret[i] = file_sizes[i - 3];
    }

    for (i = 1; i < n; ++i) {
      ret[n] += ret[i];
    }

    *psize = n + 1;
  } else {
    fprintf(stderr, "[%s, %d] response to android type invalid\n", __FILE__,
            __LINE__);
    *psize = 0;
  }

  return ret;
}

void put_task(uint8_t dev_id, musictask_t *task) {
  pthread_mutex_t *mutex_ptr =
      connfd_taskmutex_map->get(connfd_taskmutex_map, (void *)(long)dev_id);

  pthread_mutex_lock(mutex_ptr);
  musictask_t *pre_task =
      connfd_task_map->get(connfd_task_map, (void *)(long)dev_id);
  delete_musictask(pre_task);
  connfd_task_map->put(connfd_task_map, (void *)(long)dev_id, task);
}

void send_android_msg(int connfd, char msg[], int size) {
  pthread_mutex_t *mutex_ptr =
      connfd_mutex_map->get(connfd_mutex_map, (void *)(long)connfd);

  pthread_mutex_lock(mutex_ptr);

  char *p = msg;
  ssize_t n;

  while ((n = send(connfd, p, size, 0)) != 0) {
    if (n < 0) {
      delete_conn(connfd);
      break;
    }

    size -= n;
    p += n;

    if (!size) {
      break;
    }
  }

  pthread_mutex_unlock(mutex_ptr);
}

void send_file(int connfd, FILE *fp) {
  int file_block_len = 0;
  char buf[1024];

  while ((file_block_len = fread(buf, sizeof(char), sizeof(buf), fp)) > 0) {
    printf("file_block_length = %d\n", file_block_len);

    if (send(connfd, buf, file_block_len, 0) < 0) {
      fprintf(stderr, "send file failed!\n");
      break;
    }

    memset(buf, 0, sizeof(buf));
  }
}

void delete_conn(int connfd) {
  uint8_t dev_id =
      (long)connfd_dev_map->get(connfd_dev_map, (void *)(long)connfd);
  connfd_dev_map->remove(connfd_dev_map, (void *)(long)connfd);
  dev_connfd_map->remove(dev_connfd_map, (void *)(long)dev_id);

  pthread_mutex_t *mutex_ptr =
      connfd_mutex_map->get(connfd_mutex_map, (void *)(long)connfd);
  pthread_mutex_destroy(mutex_ptr);
  connfd_mutex_map->remove(connfd_mutex_map, (void *)(long)connfd);

  connfd_task_map->remove(connfd_task_map, (void *)(long)connfd);

  mutex_ptr =
      connfd_taskmutex_map->get(connfd_taskmutex_map, (void *)(long)connfd);
  pthread_mutex_destroy(mutex_ptr);
  connfd_taskmutex_map->remove(connfd_taskmutex_map, (void *)(long)connfd);

  delete_event(connfd);
}

// TODO
uint8_t strid2uint8(const char *idstr) { return 0; }

int get_connfd_by_dev_id(uint8_t dev_id) {
  void *p = dev_connfd_map->get(dev_connfd_map, (void *)(long)dev_id);

  return p ? (long)p : -1;
}
